package jmine.tec.web.servlet.cipher.impl;

import java.io.IOException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.PublicKey;

import javax.crypto.Cipher;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import jmine.tec.utils.cripto.cipher.CipherInputStream;
import jmine.tec.utils.cripto.cipher.CipherOutputStream;
import jmine.tec.utils.cripto.key.GeradorChaveRSA;
import jmine.tec.utils.cripto.key.impl.DynamicKeyPairHolder;
import jmine.tec.utils.io.IOUtils;
import jmine.tec.web.servlet.cipher.SecureCommunicationHandler;
import jmine.tec.web.servlet.cipher.challenge.Assertion;

/**
 * {@link AbstractSecureCommunicationHandler} que verifica a {@link Assertion} passada no metodo
 * {@link #nextStage(HttpServletRequest, HttpServletResponse)}
 * 
 * @author takeshi
 */
public class CheckAssertionSecureCommunicationHandler extends AbstractSecureCommunicationHandler implements SecureCommunicationHandler {

    private final Assertion challenge;

    private final transient PublicKey remotePublicKey;

    private final transient PrivateKey privateKey;

    private final transient Cipher cipher;

    private final int chunkSize;

    /**
     * C'tor
     * 
     * @param challenge {@link Assertion}
     * @param key {@link PublicKey}
     * @param privateKey {@link PrivateKey}
     * @param chunkSize int
     * @param cipher Cipher
     */
    public CheckAssertionSecureCommunicationHandler(Assertion challenge, PublicKey key, PrivateKey privateKey, int chunkSize, Cipher cipher) {
        this.challenge = challenge;
        this.remotePublicKey = key;
        this.privateKey = privateKey;
        this.chunkSize = chunkSize;
        this.cipher = GeradorChaveRSA.copyCipher(cipher);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public SecureCommunicationHandler nextStage(HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException {
        // ok so now we must read the body of the request
        if (this.remotePublicKey == null || this.privateKey == null || this.cipher == null) {
            return null;
        }
        try {
            if (checkExpectation(httpRequest)) {
                if (getLogger().isDebugEnabled()) {
                    getLogger().debug(
                            "communication stabilished with: " + httpRequest.getRemoteHost() + " @ " + httpRequest.getRemoteAddr());
                }
                KeyPair keyPair = new DynamicKeyPairHolder().get();
                Key theKey = keyPair.getPublic();
                writeKeyToClient(httpResponse, theKey);
                return new ExchangeKeysCommunicationHandler(keyPair.getPrivate(), chunkSize, cipher);
            } else {
                return expectationFailed(httpResponse);
            }
        } catch (InvalidKeyException e) {
            getLogger().warn("Cannot use the private key", e);
        }
        return this;
    }

    /**
     * Escreve a chave no httpResponse
     * 
     * @param httpResponse {@link HttpServletResponse}
     * @param theKey {@link Key}
     * @throws InvalidKeyException e
     * @throws IOException e
     */
    protected void writeKeyToClient(HttpServletResponse httpResponse, Key theKey) throws InvalidKeyException, IOException {
        CipherOutputStream cout = new CipherOutputStream(httpResponse.getOutputStream(), cipher, remotePublicKey, chunkSize);
        cout.write(theKey.getEncoded());
        cout.flush();
    }

    /**
     * Manda um erro para o cliente
     * 
     * @param httpResponse {@link HttpServletResponse}
     * @return {@link SecureCommunicationHandler}
     * @throws IOException e
     */
    protected SecureCommunicationHandler expectationFailed(HttpServletResponse httpResponse) throws IOException {
        httpResponse.sendError(HttpServletResponse.SC_CONFLICT);
        return this;
    }

    /**
     * Verifica o expectation
     * 
     * @param httpRequest {@link HttpServletRequest}
     * @return boolean
     * @throws InvalidKeyException e
     * @throws IOException e
     */
    protected boolean checkExpectation(HttpServletRequest httpRequest) throws InvalidKeyException, IOException {
        CipherInputStream cin = new CipherInputStream(httpRequest.getInputStream(), cipher, privateKey);
        String answer = new String(IOUtils.readFully(cin), "UTF-8");
        if (getLogger().isDebugEnabled()) {
            getLogger().debug("received '" + answer + "' from client. Expected: '" + challenge.getExpected() + "'");
        }
        return challenge.getExpected().equals(answer);
    }

    /**
     * @return int
     */
    public int getChunkSize() {
        return this.chunkSize;
    }

}
